#include "Graphics.h"

#ifdef _DEBUG
	#ifndef HR

	#define HR(x)					\
	{								\
		HRESULT hr = x;				\
		if (FAILED(hr))				\
			{						\
			DXTRACE_ERR(#x, hr);	\
			DebugBreak();			\
			}						\
	}
	#endif
#else
	#ifndef HR
		#define HR(x) x;
	#endif
#endif

Graphics::Graphics()
	: m_iHeight(0), m_iWidth(0)
	, m_bInitialized(false)
	, m_bInitFailed(true)
	, m_bWindowed(true)
{}

/**
 * This function need only be called once during the program
 * It calls sub-routines that initializes various aspects of Graphics
 *
 * @param hWnd is passed so that the device knows which window to draw to
 * @return wether or not the function ran without error
 */
bool Graphics::Init(HWND hWnd)
{
	
	if(m_bInitialized)					// prevent this function from being called more than once
		return !m_bInitFailed;
	m_bInitialized = true;				// flag that this function has been called

	/* if at any point a function fails, return false */
	if(!InitDevice(hWnd))
		return false;
	if(!InitManagers())
		return false;

	return true;

	// after all functions have completed successfully, mark this flag as false and return true
	m_bInitFailed = false;
	return true;
}

bool Graphics::InitDevice(HWND hWnd)
{
	/* Create a temporary IDirect3D9 object used to setup the device */
	IDirect3D9* pD3DObject = NULL;
	pD3DObject = Direct3DCreate9(D3D_SDK_VERSION);

	if(NULL == hWnd)
		return false;

	/* Setup a Rect with the window dimensions to make sure they have been set */
	RECT rClientRect;
	if(!::GetClientRect(hWnd, &rClientRect))
		throw "GetClientRect() failed";
	g_scrHeight = m_iHeight = (int)(rClientRect.bottom - rClientRect.top);
	g_scrWidth  = m_iWidth  = (int)(rClientRect.right -  rClientRect.left);

	D3DCAPS9 D3DCaps;
	HR(pD3DObject->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &D3DCaps));


	// determine what type of vertex processing to use
	DWORD dwBehaviorFlags = 0;

	if(D3DCaps.VertexProcessingCaps != 0)
		dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else
		dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;	

	//set up present parameters
	memset(&m_D3Dpp, 0, sizeof(m_D3Dpp));
	m_D3Dpp.hDeviceWindow			= hWnd;
	m_D3Dpp.Windowed				= true;				// SET TRUE FOR WINDOWE MODE
	m_D3Dpp.AutoDepthStencilFormat	= D3DFMT_D24S8;
	m_D3Dpp.EnableAutoDepthStencil	= TRUE;
	m_D3Dpp.BackBufferCount			= 1;
	m_D3Dpp.BackBufferFormat		= D3DFMT_X8R8G8B8;
	m_D3Dpp.BackBufferWidth			= g_scrWidth;
	m_D3Dpp.BackBufferHeight		= g_scrHeight;
	m_D3Dpp.SwapEffect				= D3DSWAPEFFECT_DISCARD;
	m_D3Dpp.PresentationInterval	= D3DPRESENT_INTERVAL_DEFAULT;
	m_D3Dpp.Flags					= D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
	m_D3Dpp.MultiSampleQuality		= 0;
	m_D3Dpp.MultiSampleType			= D3DMULTISAMPLE_NONE;

	// setup the d3ddevice using the d3dobject and present parameters
	HRESULT hr = pD3DObject->CreateDevice(D3DADAPTER_DEFAULT, 
										  D3DDEVTYPE_HAL, 
										  hWnd,
										  dwBehaviorFlags, 
									      &m_D3Dpp,
										  &m_pD3DDevice);
	pD3DObject->Release();
	pD3DObject = NULL;

	assert(hr == D3D_OK && "The device is broken");
	if(hr != D3D_OK)
		return false;

	return true;
}

bool Graphics::InitManagers()
{
	HRESULT hr = D3DXCreateFont(m_pD3DDevice, 18, 0, NULL, 0, false,
				   DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,
				   DEFAULT_PITCH | FF_DONTCARE, TEXT("Times New Roman"), &m_pFontManager);

	hr = D3DXCreateSprite(m_pD3DDevice, &m_pSpriteManager);

	if(hr != D3D_OK)
		return false;
	return true;
}

/**
 * Adds a single object into one of Graphics' draw queue
 * 
 * @param textureLoc is the filename of the asset
 * @param queueType determines which queue the object will go into (ie menu object will go into menu queue)
 * @return the index of the asset after being put into the queue
 */
int Graphics::AddAsset(const char* textureLoc, color cKey)
{
	// TODO: Check to see if the asset has been added before attempting to add it again
	LPDIRECT3DTEXTURE9 pTexture;
	
	// determine whether or not the image has a color key, then set it
	D3DCOLOR colorKey;
	if(!cKey.red && !cKey.green && !cKey.blue)
		colorKey = NULL;
	else
		colorKey = D3DCOLOR_ARGB(255, cKey.red, cKey.green, cKey.blue);

	// use this function instead, it allows for more control in how it is setup
	D3DXCreateTextureFromFileEx(m_pD3DDevice,								// the device
								textureLoc,									// location of the file
								D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2, // prevents rounding to even binary numbers on dimensions
								0,											// default mip mapping
								0,											// use non-dynamic texturing
								D3DFMT_UNKNOWN,								// pixel formatting - takes directly from file
								D3DPOOL_MANAGED,							// manage the memory class for us
								D3DX_DEFAULT,								// how the image is filtered (normal)
								D3DX_DEFAULT,								// how the image is filtered (mip-filtering)
								colorKey,									// don't use a color key
								NULL, NULL,									// we don't need image_info or palette, so put NULL for both
								&pTexture);									// the created texture


	// with the texture generated, create a game asset with it and it it to the list
	GameAsset* asset = new GameAsset(false, NULL, NULL, pTexture, textureLoc);
	return m_vGameAssets.push(*asset);
}

int Graphics::AddAsset(const char* meshLoc, const char* textureLoc)
{
	ID3DXMesh* pMesh;
	IDirect3DTexture9* pTexture;
	D3DXLoadMeshFromX(meshLoc, 
					  D3DXMESH_SYSTEMMEM, 
					  m_pD3DDevice, 
					  NULL, NULL, NULL, NULL, 
					  &pMesh);

	// TODO: Add optimization code for mesh loading

	D3DXCreateTextureFromFile(m_pD3DDevice, textureLoc, &pTexture);
	GameAsset* asset = new GameAsset(true, pMesh, meshLoc, pTexture, textureLoc);
	return m_vGameAssets.push(*asset);
}

bool Graphics::RemoveAsset(e_AssetType listIndex, int index)
{
	if(listIndex == GAME)
		return m_vGameAssets.remove(index);
	else if(listIndex == GUI)
		return  m_vGUIAssets.remove(index);

	return false;
}

Vector2D Graphics::GetAssetDimensions(e_AssetType listIndex, int index)
{
	Vector2D dimensions(0, 0);
	if(listIndex == GAME)
	{
		LPDIRECT3DTEXTURE9 texture;
		D3DSURFACE_DESC desc;
		texture = m_vGameAssets.getByIndex(index).pObjTex;
		texture->GetLevelDesc(0, &desc);

		dimensions.x = (float)desc.Width;
		dimensions.y = (float)desc.Height;
	}

	return dimensions;
}

/**
 * Should be called in the game loop once; draws one frame
 *
 */
void Graphics::Render()
{
	if(!m_bInitialized || m_bInitFailed)
		return;
	
	for(int i = 0; /*i < NUM_DRAWLISTS*/; ++i)
	{
		// draw active draw loops
		/*if(drawList[i].isActive)
		{
			for(int j = 0; j < drawList[i].size(); ++j)
			{
				Draw(drawList[i].getByIndex(j));
			}
		}*/
	}
	
}

void Graphics::Draw(BaseObject* pObj)
{
	Vector3D objPos = pObj->getPos();

	D3DXMatrixScaling(&m_scaleMat, 1.0f, 1.0f, 1.0f);			// Scaling
	D3DXMatrixTranslation(&m_transMat, objPos.x, objPos.y, objPos.z);			// Translating
	/*D3DXMatrixRotationYawPitchRoll(&rotMat, 
									     D3DXToRadian(pObj->getRot().x), 
									     D3DXToRadian(pObj->getRot().y), 
										 D3DXToRadian(pObj->getRot().z)); */

	m_pSpriteManager->SetTransform(&(m_scaleMat * m_rotationMat * m_transMat));
	
	// get texture


	//m_pSpriteManager->Draw(ObjTex, 0 , NULL,NULL, 0xFFFFFFFF);
}

void Graphics::Release()
{
	if(!m_bInitialized || m_bInitFailed)
		return;
	if(m_pD3DDevice != NULL)
		m_pD3DDevice->Release();
	if(m_pFontManager != NULL)
		m_pFontManager->Release();
	if(m_pSpriteManager != NULL)
		m_pSpriteManager->Release();
}

/*
 * Simply calls the release function of Graphics to ensure that there are no memory leaks
 */
Graphics::~Graphics()
{
	Release();
}